#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define BUF_SIZE 100
#define MAX_OPT_CNT 100
#define MAX_ARGS_CNT 5

/*
    num 是options的个数
    *handlers[MAX_OPT_CNT]是函数指针数组，每一个函数都有void*的函数形参和返回值
*/
struct terminal
{
    int num;
    char options[MAX_OPT_CNT][BUF_SIZE];
    void *(*handlers[MAX_OPT_CNT])(void *[]);
    void *args[MAX_OPT_CNT][MAX_ARGS_CNT];
};

/*
    向终端加入可选命令 "option"，并将该命令与处理函数 handler 绑定
    如需传入已有参数，请传递至 arg（多个参数则需封装为结构体）
*/
void add_option(struct terminal *tml, char *option, void *(*handler)(void *[]), void *arg)
{
    strcpy(tml->options[tml->num], option);
    tml->handlers[tml->num] = handler;
    tml->args[tml->num][0] = arg;
    for (int i = 1; i < MAX_ARGS_CNT; i++)
    {
        tml->args[tml->num][i] = NULL;
    }

    tml->num++;
}

/*
    解析用户写入命令
    示例：
    `download 1.txt 3`
    分割命令字符串为三个子字符串：'download', '1.txt', '3'
    终端将寻找 download 命令对应处理函数，
    剩余俩子串将作为参数传递给处理函数
*/
int parse(struct terminal *tml, char *option, int *index)
{
    char buf[BUF_SIZE];
    char ret[BUF_SIZE];
    char *save_ptr, *s = NULL;
    int ind = 1, i = 0;

    strcpy(buf, option);
    save_ptr = buf;
    if ((s = __strtok_r(save_ptr, " ", &save_ptr)))
    {
        strcpy(ret, s);
        for (i = 0; i < tml->num; i++)
        {
            if (strcmp(s, tml->options[i]) == 0)
            {
                *index = i;
                break;
            }
        }
        if (i == tml->num)
            return 0;
        s = NULL;
    }
    else
        return 0;

    while ((s = __strtok_r(save_ptr, " ", &save_ptr)))
    {
        tml->args[*index][ind] = malloc(sizeof(s) + 1);
        strcpy((char *)tml->args[*index][ind], s);
        ind++;
        s = NULL;
    }
    return 1;
}

/* 通用命令处理函数 */
void handler(struct terminal *tml, char *option)
{
    int index;

    if (parse(tml, option, &index))
    {
        (tml->handlers[index])(tml->args[index]);
        // 别忘了释放字符串参数占用的堆内存
        for (int i = 1; i < MAX_ARGS_CNT; i++)
        {
            if (tml->args[index][i])
            {
                free(tml->args[index][i]);
            }
            tml->args[index][i] = NULL;
        }
    }
    else
    {
        printf("Wrong usage! Plz check ur input!!!\n");
    }
}

/* 终端，启动～！！！ */
void run_terminal(struct terminal *tml, const char *topic)
{
    printf("Terminal V0.0.1 for %s!!!\nType 'help' for more information.\n", topic);
    while (1)
    {
        char option[BUF_SIZE];
        printf(">> ");
        fgets(option, BUF_SIZE, stdin);
        option[strlen(option) - 1] = 0;

        handler(tml, option);
    }
}